/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.client.xpath.view;

import java.awt.event.ActionEvent;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ResourceBundle;
import java.util.regex.Pattern;
import javax.jcr.PropertyType;
import javax.swing.AbstractAction;
import javax.swing.DefaultListModel;
import javax.swing.JTree;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.undo.UndoManager;
import offset.nodes.client.chooser.view.NodeChooserDialog;
import offset.nodes.Constants;
import offset.nodes.client.chooser.view.TypeChooserDialog;
import offset.nodes.client.virtual.model.GetSchemas;
import offset.nodes.client.model.NodeTypes;
import offset.nodes.client.model.Server;
import offset.nodes.client.model.SimpleNamespaceRegistry;
import offset.nodes.client.tree.model.TreeUserObjectSelectionListener;
import offset.nodes.client.tree.view.TreeConfiguratorPanel;
import offset.nodes.client.virtual.model.jcr.QName;
import offset.nodes.client.virtual.model.jcr.nodetype.NodeDefinition;
import offset.nodes.client.virtual.model.jcr.nodetype.NodeTypeDefinition;
import offset.nodes.client.virtual.model.jcr.nodetype.NodeTypeReader;
import offset.nodes.client.virtual.model.jcr.nodetype.PropertyDefinition;
import offset.nodes.client.xpath.model.ExpressionNode;
import offset.nodes.client.xpath.model.Operator;
import offset.nodes.client.xpath.model.PropertyTypeOperators;
import offset.nodes.client.xpath.model.SortColumn;
import offset.nodes.client.xpath.model.SortDirection;
import offset.nodes.client.xpath.model.ValueConstraintConfiguration;
import offset.nodes.client.xpath.model.XPathOperator;
import offset.nodes.client.xpath.model.XPathOperators;
import offset.nodes.client.xpath.model.XPathQueryReader;
import offset.nodes.client.xpath.model.XPathQueryWriter;

/**
 *
 * @author  Walter Lütgenau
 */
public class XPathPanel extends javax.swing.JPanel {

    Server server;
    SimpleNamespaceRegistry namespaces = SimpleNamespaceRegistry.getInstance();
    XPathQueryWriter query = new XPathQueryWriter();
    NodeTypeDefinition typeDefinition = null;
    NodeTypeDefinition parentTypeDefinition = null;
    ValueConstraintConfiguration treeConfiguration;
    TreeConfiguratorPanel treePanel = null;
    NodeTypes nodeTypes = null;
    PropertyAction propertyAction = new PropertyAction();
    ChildAction childAction = new ChildAction();
    ValueConstraintTreeSelectionListener valueConstraintListener = new ValueConstraintTreeSelectionListener();
    TreeNodeUpdater treeNodeUpdater = new TreeNodeUpdater();
    public static final int MODE_QUERY = 1;
    public static final int MODE_SELECT_QUERY = 2;
    int mode = MODE_QUERY;

    /** Creates new form XPathPanel */
    public XPathPanel(Server server) {
        this.server = server;
        initComponents();
        initExtended();
    }

    public void setMode(int mode) {
        this.mode = mode;
        updateType();
    }

    protected NodeTypeDefinition[] initTypes() {
        GetSchemas.Request request = new GetSchemas.Request();
        GetSchemas.Response response = null;
        try {
            response = (GetSchemas.Response) server.sendRequest(request);
            InputStream in = new ByteArrayInputStream(response.getSchemaXml().getBytes());
            NodeTypeDefinition[] definitions = NodeTypeReader.read(in);

            return definitions;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    protected NodeTypes getNodeTypes() {
        if (nodeTypes == null)
            nodeTypes = new NodeTypes(initTypes());

        return nodeTypes;
    }

    public String getXPathQuery() {
        return query.getXPathQuery();
    }

    public void setXPathQuery(NodeTypes types, String query) {
        XPathQueryReader reader = new XPathQueryReader();
        reader.read(query);

        // set all query components
        setQueryUpdateEnabled(false);

        typeTextField.setText(reader.getTypeConstraint());
        if (types == null)
            types = getNodeTypes();
        this.typeDefinition = this.parentTypeDefinition = types.get(QName.valueOf(namespaces.toQNameString(reader.getTypeConstraint())));

        updateType();
        treeConfiguration.enable();
        pathTextField.setText(reader.getPathConstraint());

        ((DefaultTreeModel) treePanel.getTree().getModel()).setRoot(reader.getValueConstraint());

        childCombo.setSelectedIndex(0);

        DefaultListModel selectedModel = (DefaultListModel) selectedList.getModel();
        selectedModel.clear();
        for (String column : reader.getColumnSpecifier()) {
            ((DefaultListModel) availableList.getModel()).removeElement(column);
            selectedModel.addElement(column);
            ((DefaultListModel) availableSortList.getModel()).addElement(column);
        }
        xpathTabbedPane.setEnabledAt(TAB_SORTING, ((DefaultListModel) selectedList.getModel()).size() > 0);

        sortTableModel.clear();
        for (SortColumn column : reader.getOrderingSpecifier()) {
            ((DefaultListModel) availableSortList.getModel()).removeElement(column.getProperty());
            sortTableModel.addSortColumn(column);
        }

        setQueryUpdateEnabled(true);
        updateQuery();
    }

    protected void initExtended() {
        treePanel = new TreeConfiguratorPanel(treeConfiguration = new ValueConstraintConfiguration(server), false, true);
        treePanel.init(null);
        treePanel.addTreeUserObjectSelectionListener(valueConstraintListener);
        treePanel.getTree().addTreeSelectionListener(new ExpressionComponentsTreeSelectionListener());

        ((DefaultListModel) selectedList.getModel()).addListDataListener(columnListener);
        selectedSortTable.getColumnModel().getColumn(SortColumn.COLUMN_ASCENDING).setMaxWidth(SortTableModel.MAX_CHECKBOX);
        selectedSortTable.getColumnModel().getColumn(SortColumn.COLUMN_DESCENDING).setMaxWidth(SortTableModel.MAX_CHECKBOX);
        selectedSortTable.getModel().addTableModelListener(new TableModelListener() {

            public void tableChanged(TableModelEvent e) {
                updateQuery();
            }
        });

        valuePanel.add(treePanel);
        updateQuery();
        updateType();
    }

    class ValueConstraintTreeSelectionListener implements TreeUserObjectSelectionListener {

        boolean enabled = true;

        public void userObjectChanged(Object newUserObject) {
            if (newUserObject instanceof ExpressionNode) {
                ExpressionNode expression = (ExpressionNode) newUserObject;
                if (expression.isEmpty()) {
                    // copy values from panel
                    expression.setChildName((String) childCombo.getSelectedItem());
                    expression.setPropertyName((String) propertyCombo.getSelectedItem());
                    expression.setPropertyType(propertyTypes.get(expression.getPropertyName()).intValue());
                    if (operatorCombo.getSelectedItem() != null)
                        expression.setOperator(((XPathOperator) operatorCombo.getSelectedItem()).getOperator());
                    expression.setValue(propertyValueTextField.getText());

                    updateQuery();
                } else {
                    boolean property = propertyAction.isEnabled();
                    propertyAction.setEnabled(false);
                    treeNodeUpdater.setEnabled(false);

                    childCombo.setSelectedItem(expression.getChildName());
                    propertyCombo.setSelectedItem(expression.getPropertyName());
                    operatorCombo.setSelectedItem(XPathOperators.get(expression.getOperator()));
                    propertyValueTextField.setText(expression.getValue());

                    propertyAction.setEnabled(property);
                    treeNodeUpdater.setEnabled(true);

                    updateQuery();
                }
            }
        }

        public boolean isEnabled() {
            return enabled;
        }

        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }
    }

    class ExpressionComponentsTreeSelectionListener implements TreeSelectionListener {

        @Override
        public void valueChanged(TreeSelectionEvent e) {
            JTree tree = (JTree) e.getSource();
            DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();

            boolean expressionPresent = root.getChildCount() > 0;
            childAction.setEnabled(expressionPresent && childCombo.getModel().getSize() > 0);
            propertyAction.setEnabled(expressionPresent);
        }
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        xpathTabbedPane = new javax.swing.JTabbedPane();
        filterTab = new javax.swing.JPanel();
        typeLabel = new javax.swing.JLabel();
        typeTextField = new javax.swing.JTextField();
        typeButton = new javax.swing.JButton();
        pathLabel = new javax.swing.JLabel();
        pathTextField = new javax.swing.JTextField();
        pathTextField.getDocument().addDocumentListener(pathDocumentListener);
        pathTextField.getDocument().addUndoableEditListener(textFieldUndoableEditListener);
        pathButton = new javax.swing.JButton();
        valueLabel = new javax.swing.JLabel();
        valuePanel = new javax.swing.JPanel();
        propertyCombo = new javax.swing.JComboBox();
        propertyValueTextField = new javax.swing.JTextField();
        propertyValueTextField.getDocument().addDocumentListener(valueDocumentListener);
        propertyValueTextField.getDocument().addUndoableEditListener(textFieldUndoableEditListener);
        operatorLabel = new javax.swing.JLabel();
        propertyValueLabel = new javax.swing.JLabel();
        propertyLabel = new javax.swing.JLabel();
        operatorCombo = new javax.swing.JComboBox();
        childCombo = new javax.swing.JComboBox();
        childLabel = new javax.swing.JLabel();
        columnsTab = new javax.swing.JPanel();
        topButton = new javax.swing.JButton();
        bottomButton = new javax.swing.JButton();
        leftButton = new javax.swing.JButton();
        rightButton = new javax.swing.JButton();
        availableLabel = new javax.swing.JLabel();
        jLabel3 = new javax.swing.JLabel();
        availablePane = new javax.swing.JScrollPane();
        availableList = new javax.swing.JList();
        selectedPane = new javax.swing.JScrollPane();
        selectedList = new javax.swing.JList();
        sortTab = new javax.swing.JPanel();
        selectedSortPane = new javax.swing.JScrollPane();
        selectedSortTable = new javax.swing.JTable();
        topSortButton = new javax.swing.JButton();
        bottomSortButton = new javax.swing.JButton();
        availableSortPane = new javax.swing.JScrollPane();
        availableSortList = new javax.swing.JList();
        rightSortButton = new javax.swing.JButton();
        leftSortButton = new javax.swing.JButton();
        resultPane = new javax.swing.JTabbedPane();
        humanDescriptionScroll = new javax.swing.JScrollPane();
        humanDescriptionPane = new javax.swing.JTextPane();
        xpathScroll = new javax.swing.JScrollPane();
        xpathPane = new javax.swing.JTextPane();

        xpathTabbedPane.setName("filter"); // NOI18N

        java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("offset/nodes/client/view/resources/ClientBundle"); // NOI18N
        typeLabel.setText(bundle.getString("xpath.filter.typeLabel")); // NOI18N

        typeButton.setText("...");
        typeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                selectType(evt);
            }
        });

        pathLabel.setText(bundle.getString("xpath.filter.pathLabel")); // NOI18N

        pathButton.setText("...");
        pathButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                selectPath(evt);
            }
        });

        valueLabel.setText(bundle.getString("xpath.filter.valueLabel")); // NOI18N

        valuePanel.setMinimumSize(new java.awt.Dimension(0, 120));
        valuePanel.setPreferredSize(new java.awt.Dimension(355, 120));
        valuePanel.setLayout(new java.awt.BorderLayout());

        propertyCombo.setAction(propertyAction);
        propertyCombo.setEnabled(false);

        propertyValueTextField.setAction(propertyAction);
        propertyValueTextField.setEnabled(false);

        operatorLabel.setText(bundle.getString("xpath.filter.operatorLabel")); // NOI18N

        propertyValueLabel.setText(bundle.getString("xpath.filter.propertyValueLabel")); // NOI18N

        propertyLabel.setText(bundle.getString("xpath.filter.propertyLabel")); // NOI18N

        operatorCombo.setAction(propertyAction);
        operatorCombo.setEnabled(false);

        childCombo.setAction(childAction);
        childCombo.setEnabled(false);

        childLabel.setText(bundle.getString("xpath.filter.childLabel")); // NOI18N

        javax.swing.GroupLayout filterTabLayout = new javax.swing.GroupLayout(filterTab);
        filterTab.setLayout(filterTabLayout);
        filterTabLayout.setHorizontalGroup(
            filterTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(filterTabLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(filterTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(filterTabLayout.createSequentialGroup()
                        .addComponent(valueLabel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addGroup(filterTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                            .addComponent(valuePanel, javax.swing.GroupLayout.DEFAULT_SIZE, 436, Short.MAX_VALUE)
                            .addGroup(filterTabLayout.createSequentialGroup()
                                .addGroup(filterTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                                    .addComponent(typeTextField, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 383, Short.MAX_VALUE)
                                    .addComponent(pathTextField, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 383, Short.MAX_VALUE))
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addGroup(filterTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                    .addComponent(typeButton)
                                    .addComponent(pathButton)))
                            .addComponent(childLabel, javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(javax.swing.GroupLayout.Alignment.LEADING, filterTabLayout.createSequentialGroup()
                                .addGroup(filterTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                                    .addComponent(childCombo, javax.swing.GroupLayout.Alignment.LEADING, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                    .addComponent(propertyCombo, javax.swing.GroupLayout.Alignment.LEADING, 0, 132, Short.MAX_VALUE)
                                    .addComponent(propertyLabel, javax.swing.GroupLayout.Alignment.LEADING))
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addGroup(filterTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                                    .addGroup(filterTabLayout.createSequentialGroup()
                                        .addComponent(operatorLabel)
                                        .addGap(36, 36, 36))
                                    .addComponent(operatorCombo, javax.swing.GroupLayout.PREFERRED_SIZE, 89, javax.swing.GroupLayout.PREFERRED_SIZE))
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addGroup(filterTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                    .addComponent(propertyValueLabel)
                                    .addComponent(propertyValueTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 203, javax.swing.GroupLayout.PREFERRED_SIZE))))
                        .addGap(35, 35, 35))
                    .addGroup(filterTabLayout.createSequentialGroup()
                        .addComponent(pathLabel)
                        .addContainerGap(485, Short.MAX_VALUE))
                    .addGroup(filterTabLayout.createSequentialGroup()
                        .addComponent(typeLabel)
                        .addContainerGap(483, Short.MAX_VALUE))))
        );
        filterTabLayout.setVerticalGroup(
            filterTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(filterTabLayout.createSequentialGroup()
                .addGap(29, 29, 29)
                .addGroup(filterTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(typeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(typeButton)
                    .addComponent(typeLabel))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(filterTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(pathButton)
                    .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(pathLabel))
                .addGap(13, 13, 13)
                .addGroup(filterTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(valueLabel)
                    .addComponent(valuePanel, javax.swing.GroupLayout.PREFERRED_SIZE, 163, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(18, 18, 18)
                .addComponent(childLabel)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(childCombo, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addGap(15, 15, 15)
                .addGroup(filterTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(operatorLabel)
                    .addComponent(propertyLabel)
                    .addComponent(propertyValueLabel))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(filterTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(operatorCombo, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(propertyCombo, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(propertyValueTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(37, 37, 37))
        );

        xpathTabbedPane.addTab("Filter", filterTab);

        topButton.setText(bundle.getString("xpath.columns.toTopLabel")); // NOI18N
        topButton.setPreferredSize(new java.awt.Dimension(61, 23));
        topButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                moveColumnUp(evt);
            }
        });

        bottomButton.setText(bundle.getString("xpath.columns.toBottomLabel")); // NOI18N
        bottomButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                moveColumnDown(evt);
            }
        });

        leftButton.setText("<-");
        leftButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                moveColumnToAvailable(evt);
            }
        });

        rightButton.setText("->");
        rightButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                moveColumnToSelected(evt);
            }
        });

        availableLabel.setText(bundle.getString("xpath.columns.availableLabel")); // NOI18N

        jLabel3.setText(bundle.getString("xpath.columns.selectedLabel")); // NOI18N

        availableList.setModel(new DefaultListModel());
        availablePane.setViewportView(availableList);

        selectedList.setModel(new DefaultListModel());
        selectedPane.setViewportView(selectedList);

        javax.swing.GroupLayout columnsTabLayout = new javax.swing.GroupLayout(columnsTab);
        columnsTab.setLayout(columnsTabLayout);
        columnsTabLayout.setHorizontalGroup(
            columnsTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(columnsTabLayout.createSequentialGroup()
                .addGap(47, 47, 47)
                .addGroup(columnsTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addGroup(columnsTabLayout.createSequentialGroup()
                        .addComponent(availablePane, javax.swing.GroupLayout.PREFERRED_SIZE, 161, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addGroup(columnsTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                            .addComponent(rightButton)
                            .addComponent(leftButton)))
                    .addComponent(availableLabel))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(columnsTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jLabel3)
                    .addGroup(columnsTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                        .addGroup(columnsTabLayout.createSequentialGroup()
                            .addComponent(topButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                            .addComponent(bottomButton))
                        .addComponent(selectedPane, javax.swing.GroupLayout.DEFAULT_SIZE, 165, Short.MAX_VALUE)))
                .addGap(89, 89, 89))
        );
        columnsTabLayout.setVerticalGroup(
            columnsTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(columnsTabLayout.createSequentialGroup()
                .addGap(37, 37, 37)
                .addGroup(columnsTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(availableLabel)
                    .addComponent(jLabel3))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(columnsTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(columnsTabLayout.createSequentialGroup()
                        .addComponent(rightButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(leftButton))
                    .addComponent(availablePane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 219, Short.MAX_VALUE)
                    .addComponent(selectedPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 219, Short.MAX_VALUE))
                .addGap(5, 5, 5)
                .addGroup(columnsTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(bottomButton)
                    .addComponent(topButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap(112, Short.MAX_VALUE))
        );

        xpathTabbedPane.addTab("Columns", columnsTab);

        selectedSortPane.setBackground(java.awt.Color.white);

        selectedSortTable.setModel(sortTableModel);
        selectedSortTable.setGridColor(java.awt.Color.white);
        selectedSortTable.setPreferredSize(new java.awt.Dimension(225, 195));
        selectedSortPane.setViewportView(selectedSortTable);

        topSortButton.setText(bundle.getString("xpath.columns.toTopLabel")); // NOI18N
        topSortButton.setPreferredSize(new java.awt.Dimension(61, 23));
        topSortButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                moveSortColumnUp(evt);
            }
        });

        bottomSortButton.setText(bundle.getString("xpath.columns.toBottomLabel")); // NOI18N
        bottomSortButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                moveSortColumnDown(evt);
            }
        });

        availableSortList.setModel(new DefaultListModel());
        availableSortPane.setViewportView(availableSortList);

        rightSortButton.setText("->");
        rightSortButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                moveSortColumnToSelected(evt);
            }
        });

        leftSortButton.setText("<-");
        leftSortButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                moveSortColumnToAvailable(evt);
            }
        });

        javax.swing.GroupLayout sortTabLayout = new javax.swing.GroupLayout(sortTab);
        sortTab.setLayout(sortTabLayout);
        sortTabLayout.setHorizontalGroup(
            sortTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(sortTabLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(sortTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(sortTabLayout.createSequentialGroup()
                        .addComponent(availableSortPane, javax.swing.GroupLayout.PREFERRED_SIZE, 149, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(sortTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                            .addComponent(rightSortButton)
                            .addComponent(leftSortButton))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(selectedSortPane, javax.swing.GroupLayout.DEFAULT_SIZE, 293, Short.MAX_VALUE))
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, sortTabLayout.createSequentialGroup()
                        .addComponent(topSortButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(bottomSortButton)))
                .addContainerGap())
        );
        sortTabLayout.setVerticalGroup(
            sortTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(sortTabLayout.createSequentialGroup()
                .addGap(111, 111, 111)
                .addGroup(sortTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(sortTabLayout.createSequentialGroup()
                        .addComponent(rightSortButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(leftSortButton)
                        .addContainerGap())
                    .addGroup(sortTabLayout.createSequentialGroup()
                        .addGroup(sortTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(selectedSortPane, javax.swing.GroupLayout.DEFAULT_SIZE, 210, Short.MAX_VALUE)
                            .addComponent(availableSortPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 210, Short.MAX_VALUE))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(sortTabLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(bottomSortButton)
                            .addComponent(topSortButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addGap(66, 66, 66))))
        );

        xpathTabbedPane.addTab("Sorting", sortTab);

        humanDescriptionScroll.setViewportView(humanDescriptionPane);

        resultPane.addTab(bundle.getString("xpath.filter.humanDescriptionTab"), humanDescriptionScroll); // NOI18N

        xpathScroll.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.LOWERED));

        xpathPane.setBorder(null);
        xpathPane.setMinimumSize(new java.awt.Dimension(6, 100));
        xpathPane.setPreferredSize(new java.awt.Dimension(6, 85));
        xpathScroll.setViewportView(xpathPane);

        resultPane.addTab(bundle.getString("xpath.filter.xpathTab"), xpathScroll); // NOI18N

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(resultPane, javax.swing.GroupLayout.PREFERRED_SIZE, 526, Short.MAX_VALUE)
                .addContainerGap())
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(layout.createSequentialGroup()
                    .addContainerGap()
                    .addComponent(xpathTabbedPane, javax.swing.GroupLayout.DEFAULT_SIZE, 526, Short.MAX_VALUE)
                    .addContainerGap()))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addGap(478, 478, 478)
                .addComponent(resultPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGap(32, 32, 32))
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(layout.createSequentialGroup()
                    .addContainerGap()
                    .addComponent(xpathTabbedPane, javax.swing.GroupLayout.PREFERRED_SIZE, 441, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addContainerGap(187, Short.MAX_VALUE)))
        );
    }// </editor-fold>//GEN-END:initComponents
    private static final int TAB_FILTER = 0;
    private static final int TAB_COLUMNS = 1;
    private static final int TAB_SORTING = 2;

    private void selectType(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectType
        TypeChooserDialog dialog = new TypeChooserDialog(null, true, server);
        dialog.setVisible(true);
        if (dialog.getReturnStatus() == TypeChooserDialog.RET_OK) {
            this.typeDefinition = this.parentTypeDefinition = dialog.getDefinition();
            typeTextField.setText(namespaces.toPrefixName(typeDefinition.getName()));
            updateQuery();
            updateType();
            treeConfiguration.enable();
        }
    }//GEN-LAST:event_selectType

    private void selectPath(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectPath
        try {
            NodeChooserDialog dialog = new NodeChooserDialog(null, true, server, "/", "*");
            dialog.setVisible(true);
            if (dialog.getReturnStatus() == NodeChooserDialog.RET_OK) {
                pathTextField.setText(dialog.getPath());
                updateQuery();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }//GEN-LAST:event_selectPath

    class PropertyAction extends AbstractAction {

        public PropertyAction() {
            setEnabled(false);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (!enabled)
                return;

            updateProperty();
        }

        private void updateProperty() {
            if (propertyCombo.getItemCount() == 0)
                return;

            propertyValueTextField.setText("");
            treeNodeUpdater.updateTreeNode();
        }
    }

    private void moveColumnToSelected(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_moveColumnToSelected
        Object[] toSelected = availableList.getSelectedValues();
        if (toSelected == null)
            return;
        for (int i = 0; i < toSelected.length; i++) {
            ((DefaultListModel) selectedList.getModel()).addElement(toSelected[i]);
            ((DefaultListModel) availableSortList.getModel()).addElement(toSelected[i]);
            ((DefaultListModel) availableList.getModel()).removeElement(toSelected[i]);
        }
        xpathTabbedPane.setEnabledAt(TAB_SORTING, ((DefaultListModel) selectedList.getModel()).size() > 0);
}//GEN-LAST:event_moveColumnToSelected

    private void moveColumnToAvailable(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_moveColumnToAvailable
        Object[] toAvailable = selectedList.getSelectedValues();
        if (toAvailable == null)
            return;
        for (int i = 0; i < toAvailable.length; i++) {
            ((DefaultListModel) availableList.getModel()).addElement(toAvailable[i]);
            ((DefaultListModel) selectedList.getModel()).removeElement(toAvailable[i]);
            ((DefaultListModel) availableSortList.getModel()).removeElement(toAvailable[i]);
            sortTableModel.removeSortColumn((String) toAvailable[i]);
        }
        xpathTabbedPane.setEnabledAt(TAB_SORTING, ((DefaultListModel) selectedList.getModel()).size() > 0);
}//GEN-LAST:event_moveColumnToAvailable

    private void moveColumnUp(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_moveColumnUp
        Object[] up = selectedList.getSelectedValues();
        int[] selectionResult = new int[up.length];
        DefaultListModel model = (DefaultListModel) selectedList.getModel();

        int j = 0;
        for (int i = 0; i < up.length; i++) {
            int index = model.indexOf(up[i]);
            if (index > 0) {
                model.removeElement(up[i]);
                model.add(selectionResult[j++] = index - 1, up[i]);
            }
        }
        for (int selected : selectionResult) {
            selectedList.getSelectionModel().addSelectionInterval(selected, selected);
        }
}//GEN-LAST:event_moveColumnUp

    private void moveColumnDown(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_moveColumnDown
        Object[] down = selectedList.getSelectedValues();
        int[] selectionResult = new int[down.length];
        DefaultListModel model = (DefaultListModel) selectedList.getModel();

        int j = 0;
        for (int i = down.length - 1; i >= 0; i--) {
            int index = model.indexOf(down[i]);
            if (index < model.size() - 1) {
                model.removeElement(down[i]);
                model.add(selectionResult[j++] = index + 1, down[i]);
            }
        }
        for (int selected : selectionResult) {
            selectedList.getSelectionModel().addSelectionInterval(selected, selected);
        }
}//GEN-LAST:event_moveColumnDown

    private void moveSortColumnUp(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_moveSortColumnUp
        int[] up = selectedSortTable.getSelectedRows();
        int[] selectionResult = sortTableModel.moveUp(up);
        for (int selected : selectionResult) {
            selectedSortTable.getSelectionModel().addSelectionInterval(selected, selected);
        }
}//GEN-LAST:event_moveSortColumnUp

    private void moveSortColumnDown(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_moveSortColumnDown
        int[] down = selectedSortTable.getSelectedRows();
        int[] selectionResult = sortTableModel.moveDown(down);
        for (int selected : selectionResult) {
            selectedSortTable.getSelectionModel().addSelectionInterval(selected, selected);
        }
}//GEN-LAST:event_moveSortColumnDown

    private void moveSortColumnToSelected(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_moveSortColumnToSelected
        Object[] toSelected = availableSortList.getSelectedValues();
        if (toSelected == null)
            return;
        for (int i = 0; i < toSelected.length; i++) {
            sortTableModel.addSortColumn(new SortColumn((String) toSelected[i], SortDirection.ascending));
            ((DefaultListModel) availableSortList.getModel()).removeElement(toSelected[i]);
        }
}//GEN-LAST:event_moveSortColumnToSelected

    private void moveSortColumnToAvailable(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_moveSortColumnToAvailable
        int[] toAvailable = selectedSortTable.getSelectedRows();
        if (toAvailable == null)
            return;
        for (int i = 0; i < toAvailable.length; i++) {
            ((DefaultListModel) availableSortList.getModel()).addElement(sortTableModel.getSortColumn(toAvailable[i]).getProperty());
            sortTableModel.removeSortColumn(toAvailable[i]);
        }

}//GEN-LAST:event_moveSortColumnToAvailable

    class ChildAction extends AbstractAction {

        public ChildAction() {
            setEnabled(false);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (!enabled)
                return;

            updateChild();
        }

        protected void updateChild() {
            if (childCombo.getItemCount() == 0)
                return;

            String childName = (String) childCombo.getSelectedItem();
            if (childName.length() == 0) {
                typeDefinition = parentTypeDefinition;
                updateType(namespaces.toPrefixName(typeDefinition.getName()), false);
            } else
                for (NodeDefinition childDefinition : parentTypeDefinition.getDeclaredChildNodeDefinitions()) {
                    String name = namespaces.toPrefixName(childDefinition.getName());
                    if (name.equals(childName)) {
                        typeDefinition = getNodeTypes().get(childDefinition.getDefaultPrimaryType());
                        updateType(namespaces.toPrefixName(typeDefinition.getName()), false);
                    }
                }

            propertyAction.actionPerformed(null);
        }
    }

    static class SortTableModel extends AbstractTableModel {

        public static final int MAX_CHECKBOX = 70;
        LinkedList<SortColumn> sortColumns = new LinkedList<SortColumn>();
        String[] columnKeys = new String[]{"sortColumn.columnName", "sortColumn.columnAscending", "sortColumn.columnDescending"};
        ResourceBundle bundle = ResourceBundle.getBundle("offset/nodes/client/view/resources/ClientBundle");

        public SortTableModel() {
            sortColumns = new LinkedList<SortColumn>();
        }

        public void addSortColumn(SortColumn sortColumn) {
            sortColumns.add(sortColumn);
            fireTableDataChanged();
        }

        public void removeSortColumn(int row) {
            sortColumns.remove(row);
            fireTableDataChanged();
        }

        public void removeSortColumn(String propertyName) {
            Iterator<SortColumn> i = sortColumns.iterator();
            while (i.hasNext()) {
                SortColumn column = i.next();
                if (column.getProperty().equals(propertyName)) {
                    i.remove();
                    return;
                }
            }
        }

        public SortColumn getSortColumn(int row) {
            return sortColumns.get(row);
        }

        public SortColumn[] getSortColumns() {
            return sortColumns.toArray(new SortColumn[sortColumns.size()]);
        }

        public int[] moveUp(int[] rows) {
            int[] result = new int[rows.length];

            int j = 0;
            for (int row : rows) {
                if (row <= 0 || row >= sortColumns.size())
                    continue;

                SortColumn down = sortColumns.get(row - 1);
                SortColumn up = sortColumns.get(row);
                sortColumns.set(row - 1, up);
                sortColumns.set(row, down);

                result[j++] = row - 1;
            }

            fireTableDataChanged();

            if (j < rows.length) {
                int[] newResult = new int[j];
                System.arraycopy(result, 0, newResult, 0, j);
                result = newResult;
            }
            return result;
        }

        public int[] moveDown(int[] rows) {
            int[] result = new int[rows.length];

            int j = 0;
            for (int row : rows) {
                if (row < 0 || row >= sortColumns.size() - 1)
                    return new int[0];

                SortColumn up = sortColumns.get(row + 1);
                SortColumn down = sortColumns.get(row);
                sortColumns.set(row, up);
                sortColumns.set(row + 1, down);

                result[j++] = row + 1;
            }

            fireTableDataChanged();

            if (j < rows.length) {
                int[] newResult = new int[j];
                System.arraycopy(result, 0, newResult, 0, j);
                result = newResult;
            }
            return result;
        }

        public void clear() {
            sortColumns.clear();
        }

        public int getRowCount() {
            return sortColumns.size();
        }

        @Override
        public int getColumnCount() {
            return 3;
        }

        @Override
        public String getColumnName(int column) {
            return bundle.getString(columnKeys[column]);
        }
        Class types[] = new Class[]{String.class, Boolean.class, Boolean.class};

        @Override
        public Class<?> getColumnClass(int column) {
            if (column < 0 || column >= getColumnCount())
                return null;
            return types[column];
        }

        @Override
        public Object getValueAt(int row, int column) {
            if (row < 0 || row >= sortColumns.size() || column < 0 || column >= getColumnCount())
                return null;

            SortColumn sortColumn = sortColumns.get(row);
            return sortColumn.toArray()[column];
        }

        @Override
        public void setValueAt(Object value, int row, int column) {
            if (row < 0 || row >= sortColumns.size() || column < 0 || column >= getColumnCount())
                return;

            SortColumn sortColumn = sortColumns.get(row);
            switch (column) {
                case SortColumn.COLUMN_PROPERTY:
                    sortColumn.setProperty((String) value);
                    break;

                case SortColumn.COLUMN_ASCENDING:
                    if (((Boolean) value).booleanValue())
                        sortColumn.setDirection(SortDirection.ascending);
                    else
                        sortColumn.setDirection(SortDirection.descending);
                    fireTableRowsUpdated(row, row);
                    break;

                case SortColumn.COLUMN_DESCENDING:
                    if (((Boolean) value).booleanValue())
                        sortColumn.setDirection(SortDirection.descending);
                    else
                        sortColumn.setDirection(SortDirection.ascending);
                    fireTableRowsUpdated(row, row);
                    break;
            }
        }

        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
            if (columnIndex == SortColumn.COLUMN_ASCENDING || columnIndex == SortColumn.COLUMN_DESCENDING)
                return true;
            return false;
        }
    }
    SortTableModel sortTableModel = new SortTableModel();

    class ValueDocumentListener implements DocumentListener {

        public void changedUpdate(DocumentEvent e) {
            treeNodeUpdater.updateTreeNode();
        }

        public void insertUpdate(DocumentEvent e) {
            treeNodeUpdater.updateTreeNode();
        }

        public void removeUpdate(DocumentEvent e) {
            treeNodeUpdater.updateTreeNode();
        }
    }
    ValueDocumentListener valueDocumentListener = new ValueDocumentListener();

    class PathDocumentListener implements DocumentListener {

        public void changedUpdate(DocumentEvent e) {
            updateQuery();
        }

        public void insertUpdate(DocumentEvent e) {
            updateQuery();
        }

        public void removeUpdate(DocumentEvent e) {
            updateQuery();
        }
    }
    PathDocumentListener pathDocumentListener = new PathDocumentListener();
    ListDataListener columnListener = new ListDataListener() {

        public void contentsChanged(ListDataEvent e) {
            updateQuery();
        }

        public void intervalAdded(ListDataEvent e) {
            updateQuery();
        }

        public void intervalRemoved(ListDataEvent e) {
            updateQuery();
        }
    };
    UndoableEditListener textFieldUndoableEditListener = new UndoableEditListener() {

        UndoManager undo = new UndoManager();

        @Override
        public void undoableEditHappened(UndoableEditEvent e) {
            undo.addEdit(e.getEdit());
            if (!checkPropertyValue())
                undo.undo();
        }
    };

    class TreeNodeUpdater {

        boolean enabled = true;

        /**
         * Update the currently selected tree node from the values in the property, operator and value controls.
         */
        protected void updateTreeNode() {
            if (!isEnabled())
                return;

            if (treePanel.getTree().getSelectionPath() == null
                    || treePanel.getTree().getModel().getChildCount(treePanel.getTree().getModel().getRoot()) == 0)
                return;

            DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) treePanel.getTree().getSelectionPath().getLastPathComponent();
            if (treeNode.getUserObject() instanceof ExpressionNode) {
                ExpressionNode expression = (ExpressionNode) treeNode.getUserObject();

                // copy values from panel
                expression.setChildName((String) childCombo.getSelectedItem());
                expression.setPropertyName((String) propertyCombo.getSelectedItem());
                expression.setPropertyType(propertyTypes.get(expression.getPropertyName()).intValue());
                if (operatorCombo.getSelectedItem() != null)
                    expression.setOperator(((XPathOperator) operatorCombo.getSelectedItem()).getOperator());
                expression.setValue(propertyValueTextField.getText());

                ((DefaultTreeModel) treePanel.getTree().getModel()).nodeChanged(treeNode);
                updateQuery();
            }
        }

        public boolean isEnabled() {
            return enabled;
        }

        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }
    }

    protected boolean isEmpty(String value) {
        if (value == null || value.length() == 0)
            return true;
        return false;
    }
    boolean queryUpdateEnabled = true;

    public boolean isQueryUpdateEnabled() {
        return queryUpdateEnabled;
    }

    public void setQueryUpdateEnabled(boolean queryUpdateEnabled) {
        this.queryUpdateEnabled = queryUpdateEnabled;
    }

    /**
     * Update the query object and subsequently the XPath and human query representations. Update the disabled/enabled setting of 
     * controls depending on the state of other controls
     */
    protected void updateQuery() {
        if (!isQueryUpdateEnabled())
            return;

        // set all query components
        query.setTypeConstraint(typeTextField.getText());
        query.setPathConstraint(pathTextField.getText());

        DefaultMutableTreeNode valueConstraint = (DefaultMutableTreeNode) ((DefaultTreeModel) treePanel.getTree().getModel()).getRoot();
        query.setValueConstraint(valueConstraint);

        Object[] selectedColumns = ((DefaultListModel) selectedList.getModel()).toArray();
        String[] selectedColumnNames = new String[selectedColumns.length];
        for (int i = 0; i < selectedColumns.length; i++) {
            selectedColumnNames[i] = (String) selectedColumns[i];
        }
        query.setColumnSpecifier(selectedColumnNames);

        query.setOrderingSpecifier(sortTableModel.getSortColumns());

        // display resulting text representations
        xpathPane.setText(query.getXPathQuery());
        humanDescriptionPane.setText(query.getHumanDescription());
    }

    /**
     * Check the property value against the property type.
     * Unfortunately, regular expression cannot be used, as there is no
     * way to test a string shorter than the pattern against a pattern.
     *
     * @return true in case it conforms to the type, else false.
     */
    protected boolean checkPropertyValue() {
        String propertyName = (String) propertyCombo.getSelectedItem();
        String propertyValue = propertyValueTextField.getText();

        if (propertyName == null || typeDefinition == null || propertyValue == null || propertyValue.length() == 0 || !propertyTypes.containsKey(propertyName))
            return true;

        int propertyType = propertyTypes.get(propertyName).intValue();

        boolean result = true;
        switch (propertyType) {
            case PropertyType.LONG:
                if (!isLong(propertyValue))
                    result = false;
                break;

            case PropertyType.DOUBLE:
                if (!isDouble(propertyValue))
                    result = false;
                break;

            case PropertyType.BOOLEAN:
                if (!isBoolean(propertyValue))
                    result = false;
                break;

            case PropertyType.DATE:
                if (!isDate(propertyValue))
                    result = false;
                break;
        }

        return result;
    }

    protected boolean isLong(String value) {
        for (int i = 0; i < value.length(); i++) {
            if (!Character.isDigit(value.charAt(i)))
                return false;
        }

        return true;
    }

    protected boolean isDouble(String value) {
        if (value.startsWith("-") || value.startsWith("+"))
            value = value.substring(1);

        if (value.length() == 0)
            return true;

        int i;
        for (i = 0; i < value.length(); i++) {
            char c = value.charAt(i);
            if (!Character.isDigit(c)) {
                if (c != '.')
                    return false;
                break;
            }
        }

        if (i == value.length() - 1)
            return true;

        value = value.substring(i + 1);

        for (i = 0; i < value.length(); i++) {
            char c = value.charAt(i);
            if (!Character.isDigit(c))
                return false;
        }

        return true;
    }

    protected boolean isBoolean(String value) {
        String[] booleanValues = {"true", "false"};

        for (String booleanValue : booleanValues) {
            if (booleanValue.startsWith(value.toLowerCase()))
                return true;
        }

        return false;
    }

    protected boolean isDate(String value) {
        String pattern = "2999-19-39T24:69:69";

        for (int i = 0; i < value.length(); i++) {
            if (Character.isDigit(pattern.charAt(i))) {
                if (!Character.isDigit(value.charAt(i)))
                    return false;
                if (value.charAt(i) > pattern.charAt(i))
                    return false;
            } else if (pattern.charAt(i) != value.charAt(i))
                return false;
        }

        return true;
    }

    private int isNumber(String value, int maxLength) {
        int i;
        for (i = 0; i < value.length() && i < maxLength; i++) {
            char c = value.charAt(i);
            if (!Character.isDigit(c))
                return -1;
        }

        return i;
    }

    private int isString(String[] values, String pattern) {
        for (String value : values) {
            if (pattern.startsWith(value))
                return value.length();
        }
        return -1;
    }
    private static Pattern longPattern = Pattern.compile("[0-9]*");
    private static Pattern doublePattern = Pattern.compile("[+-]?[0-9]+(\\.)?[0-9]*");
    private static Pattern booleanPattern = Pattern.compile("true|TRUE|false|FALSE");
    private static Pattern datePattern = Pattern.compile("[1-2]{1}[0-9]{3}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}");
    HashMap<String, Integer> propertyTypes;

    protected void addTypeProperties(NodeTypeDefinition typeDefinition) {
        PropertyDefinition[] properties = typeDefinition.getDeclaredPropertyDefinitions();
        for (int i = 0; i
                < properties.length; i++) {
            if (properties[i].getRequiredType() == PropertyType.NAME
                    || properties[i].getRequiredType() == PropertyType.UNDEFINED
                    || properties[i].getRequiredType() == PropertyType.PATH
                    || properties[i].getRequiredType() == PropertyType.STRING
                    || properties[i].getRequiredType() == PropertyType.REFERENCE
                    || properties[i].getRequiredType() == PropertyType.LONG
                    || properties[i].getRequiredType() == PropertyType.DOUBLE
                    || properties[i].getRequiredType() == PropertyType.BOOLEAN
                    || properties[i].getRequiredType() == PropertyType.DATE) {
                String propertyName = namespaces.toPrefixName(properties[i].getName());
                propertyTypes.put(propertyName, new Integer(properties[i].getRequiredType()));
                propertyCombo.addItem(propertyName);
                ((DefaultListModel) availableList.getModel()).addElement(propertyName);
            }
        }

        for (int i = 0; i < typeDefinition.getSupertypes().length; i++) {
            NodeTypeDefinition superType = getNodeTypes().get(typeDefinition.getSupertypes()[i]);
            addTypeProperties(superType);
        }

    }

    protected void addChildren(NodeTypeDefinition typeDefinition) {
        NodeDefinition[] childDefinitions = typeDefinition.getDeclaredChildNodeDefinitions();

        for (NodeDefinition childDefinition : childDefinitions) {
            childCombo.addItem(namespaces.toPrefixName(childDefinition.getName()));
        }

        for (int i = 0; i < typeDefinition.getSupertypes().length; i++) {
            NodeTypeDefinition superType = getNodeTypes().get(typeDefinition.getSupertypes()[i]);
            addChildren(superType);
        }
    }

    protected void updateType() {
        updateType(typeTextField.getText(), true);
    }

    protected void updateType(String type, boolean updateChildren) {
        boolean child = childAction.isEnabled();
        childAction.setEnabled(false);
        boolean property = propertyAction.isEnabled();
        propertyAction.setEnabled(false);

        // enable/disable controls based on type
        boolean hasType = !isEmpty(type);

        xpathTabbedPane.setEnabledAt(TAB_COLUMNS, hasType);
        xpathTabbedPane.setEnabledAt(TAB_SORTING, false);

        if (updateChildren)
            childCombo.removeAllItems();
        propertyCombo.removeAllItems();
        operatorCombo.removeAllItems();
        ((DefaultListModel) availableList.getModel()).removeAllElements();
        ((DefaultListModel) selectedList.getModel()).removeAllElements();

        if (typeDefinition != null) {
            if (updateChildren)
                childCombo.addItem("");
            propertyTypes = new HashMap<String, Integer>();

            addTypeProperties(typeDefinition);
            if (updateChildren)
                addChildren(typeDefinition);
            ((DefaultListModel) selectedList.getModel()).addElement(Constants.JCR_PATH);
            if (mode == MODE_SELECT_QUERY)
                ((DefaultListModel) availableList.getModel()).addElement(QName.NAME_PROPERTY.getLocalName());

            if (typeDefinition.getDeclaredPropertyDefinitions().length > 0) {
                availableList.setSelectedIndex(0);

                Operator[] operators = PropertyTypeOperators.getOperators(typeDefinition.getDeclaredPropertyDefinitions()[0].getRequiredType());
                for (int i = 0; i < operators.length; i++) {
                    operatorCombo.addItem(XPathOperators.get(operators[i]));
                }
            } else
                operatorCombo.removeAllItems();
        }

        childAction.setEnabled(child);
        propertyAction.setEnabled(property);
        treeNodeUpdater.updateTreeNode();
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JLabel availableLabel;
    private javax.swing.JList availableList;
    private javax.swing.JScrollPane availablePane;
    private javax.swing.JList availableSortList;
    private javax.swing.JScrollPane availableSortPane;
    private javax.swing.JButton bottomButton;
    private javax.swing.JButton bottomSortButton;
    private javax.swing.JComboBox childCombo;
    private javax.swing.JLabel childLabel;
    private javax.swing.JPanel columnsTab;
    private javax.swing.JPanel filterTab;
    private javax.swing.JTextPane humanDescriptionPane;
    private javax.swing.JScrollPane humanDescriptionScroll;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JButton leftButton;
    private javax.swing.JButton leftSortButton;
    private javax.swing.JComboBox operatorCombo;
    private javax.swing.JLabel operatorLabel;
    private javax.swing.JButton pathButton;
    private javax.swing.JLabel pathLabel;
    private javax.swing.JTextField pathTextField;
    private javax.swing.JComboBox propertyCombo;
    private javax.swing.JLabel propertyLabel;
    private javax.swing.JLabel propertyValueLabel;
    private javax.swing.JTextField propertyValueTextField;
    private javax.swing.JTabbedPane resultPane;
    private javax.swing.JButton rightButton;
    private javax.swing.JButton rightSortButton;
    private javax.swing.JList selectedList;
    private javax.swing.JScrollPane selectedPane;
    private javax.swing.JScrollPane selectedSortPane;
    private javax.swing.JTable selectedSortTable;
    private javax.swing.JPanel sortTab;
    private javax.swing.JButton topButton;
    private javax.swing.JButton topSortButton;
    private javax.swing.JButton typeButton;
    private javax.swing.JLabel typeLabel;
    private javax.swing.JTextField typeTextField;
    private javax.swing.JLabel valueLabel;
    private javax.swing.JPanel valuePanel;
    private javax.swing.JTextPane xpathPane;
    private javax.swing.JScrollPane xpathScroll;
    private javax.swing.JTabbedPane xpathTabbedPane;
    // End of variables declaration//GEN-END:variables
}
